<html lang="zh">
<head>
    <title>Api 文档</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.8"></script>
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.4/dist/jsoneditor.min.css">
    <script src="https://cdn.jsdelivr.net/npm/jsoneditor@9.10.4/dist/jsoneditor.min.js" ></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <link rel="stylesheet" href="https://unpkg.com/codemirror@5.65.8/lib/codemirror.css">
    <link rel="stylesheet" href="https://unpkg.com/codemirror@5.65.8/theme/material.css">
    <script src="https://unpkg.com/codemirror@5.65.8/lib/codemirror.js"></script>
    <script src="https://unpkg.com/codemirror@5.65.8/mode/javascript/javascript.js"></script>
    <style>
        html,body,#app,.el-container{
            height: 100%;
            margin: 0;
        }
        .el-aside{
            background-color: #545c64;
        }

        #url .el-link{
            font-size: 18px;
            line-height: 30px;
        }
        .el-alert{
            margin-bottom: 10px;
        }
        .el-form--label-top .el-form-item__label{
            line-height: 35px;
            padding: 0;
        }
        .el-form-item{
            margin-bottom: 15px;
        }
        #send{
            font-size: 25px;position: relative;color: #67C23A;
        }
        #send:hover{
            color: #67C23A99;
        }
        .CodeMirror {
            width: 100%; height: calc(100% -  173px);
        }
        .upload-demo{
            position: absolute;
            top: 50%;
            left: 50%;
            margin-left: -30px;
            margin-top: -90px;
        }
        .el-dialog__header{
            background-color: #909399;
            padding: 15px 20px;
        }
        .el-dialog__title,.el-dialog__headerbtn .el-dialog__close{
            color: white;
        }
        .t-add .cell, .re-add {
            color: #67C23A !important;
        }
        .t-del .cell, .re-del {
            text-decoration: line-through;
            color: #F56C6C !important;
        }
    </style>
</head>
<body>
<div id="app">
    <el-container>
        <el-aside width="200px" style="overflow-x: hidden">
            <el-menu
                    :default-active="apiActive"
                    class="el-menu-vertical-demo"
                    @select="handleSelect"
                    background-color="#545c64"
                    text-color="#fff"
                    active-text-color="#ffd04b">
                <el-submenu v-for="(item, index) in ApiInfo.apiLists" v-if="searchP(item)" :index="index">
                    <template slot="title">
                        <i :class="item.children.filter(a => myMark.includes(a.url)).length > 0 ? 'el-icon-star-on' : 'el-icon-star-off'"
                           :style="{color: item.children.length > item.children.filter(a => myMark.includes(a.url)).length ? '#E6A23C' : 'red' }"></i>
                        <span>{{item.name}}</span>
                    </template>
                    <el-menu-item v-for="(api, Aindex) in item.children" v-if="search(api)" :index="index + '-' + Aindex">
                        <i :class="myMark.includes(api.url) ? 'el-icon-star-on' : 'el-icon-star-off'" style="color: #E6A23C"></i>
                        <span slot="title">
                            <span v-if="api.version.filter(v => v < 0).length <= 0">{{api.name}}</span>
                            <span v-else  class="re-del">{{api.name}}</span>
                        </span>
                    </el-menu-item>
                </el-submenu>
            </el-menu>
        </el-aside>
        <el-container>
            <el-header>
                <el-button style="margin-top: 8px" type="primary" plain  @click="docDesVisible = true">文档说明</el-button>
                <el-button style="margin-top: 8px" type="primary" plain @click="showMark = !showMark">我的标记</el-button>
                <el-button style="margin-top: 8px" type="primary" plain @click="getApiInfo">更新</el-button>
                检索：<el-input v-model="searchText" style="width: 200px" placeholder="搜索：地址|名字" clearable></el-input>
                版本检索：<el-select v-model="version" style="width: 200px"  placeholder="版本号">
                    <el-option v-for="vv in ApiInfo.version.map(vs => Math.abs(vs)).filter((v1, i1, s1) => s1.indexOf(v1) === i1)" :value="vv" :label="'版本：' + vv"></el-option>
                </el-select>
            </el-header>
            <el-main>
                <el-row  v-if="currentApiInfo.name" :gutter="10">
                    <el-col :span="12">
                        <el-card class="box-card">
                            <div slot="header" class="clearfix">
                                <i :class="myMark.includes(currentApiInfo.url) ? 'el-icon-star-on' : 'el-icon-star-off'" @click="mark(currentApiInfo.url)" style="color: #E6A23C;font-size: 25px;position: relative;top: 3px"></i>
                                <el-tag>{{currentApiInfo.method}}</el-tag>
                                <el-link type="primary">{{ApiInfo.host}}{{currentApiInfo.url}}</el-link>
                                <el-link v-if="currentApiInfo.version.filter(v => v < 0).length <= 0" :icon="sendIcon" type="success" @click="send" style="font-size:18px"></el-link>
                                <el-link v-else disabled> 版本 {{Math.abs(ApiInfo.version.filter(v => v <= 0)[0])}} 已废弃</el-link>
                            </div>
                            <el-tabs v-model="paramShowType" @tab-click="paramShowTypeToggle">
                                <el-tab-pane label="表单" name="form">
                                    <el-form ref="form" :model="form" label-position="top" label-width="80px">
                                        <template v-for="request in currentApiInfo.requestParams">
                                            <template v-if="!request.children || request.children.length === 0">
                                                <el-form-item >
                                                    <template slot="label">
                                                        <b style="color:#E6A23C" v-if="request.version !== 1">V{{Math.abs(request.version)}}</b>
                                                        <el-link type="primary" :class="{'re-del':request.version < 0, 're-add':request.version > 1}" :underline="false">[{{ request.name }}]</el-link>
                                                        <i style="color: #cccccc"><{{request.type}}></i>{{request.describe}}
                                                    </template>
                                                    <el-input v-if="request.type !== 'Array'"  v-model="form[request.name]"></el-input>
                                                    <el-select style="width: 100%" v-else multiple allow-create filterable default-first-option v-model="form[request.name]"></el-select>
                                                </el-form-item>
                                            </template>
                                            <template v-else-if="request.children.length > 0">
                                                <template v-for="requestChildren in request.children">
                                                    <el-form-item>
                                                        <template slot="label">
                                                            <b style="color:#E6A23C" v-if="request.version !== 1">V{{Math.abs(request.version)}}</b>
                                                            <el-link type="primary" :class="{'re-del':request.version < 0, 're-add':request.version > 1}"  :underline="false">[{{ request.name }}.{{requestChildren.name}}]</el-link>
                                                            <i style="color: #cccccc"><{{requestChildren.type}}></i>{{request.describe}}.{{requestChildren.describe}}
                                                        </template>
                                                        <el-input v-if="request.type === 'Object'" v-model="form[request.name][requestChildren.name]"></el-input>
                                                        <el-input v-else value="请使用json组件模拟数据" readonly=""></el-input>
                                                    </el-form-item>
                                                </template>
                                            </template>
                                        </template>
                                    </el-form>
                                </el-tab-pane>
                                <el-tab-pane label="JSON" name="json">
                                    <div id="paramShowJson" style="width: 100%; height: calc(100% -  173px);"></div>
                                </el-tab-pane>
                                <el-tab-pane label="请求前脚本" name="scriptBefore">
                                    <textarea id="requestBefore" ></textarea>
                                </el-tab-pane>
                                <el-tab-pane label="请求后脚本" name="scriptAfter">
                                    <textarea id="requestAfter" ></textarea>
                                </el-tab-pane>
                                <el-tab-pane label="请求响应" name="response">
                                    <div v-if="typeof this.response === 'object'">
                                        <div id="responseJson" style="width: 100%; height: calc(100% -  173px);"></div>
                                    </div>
                                    <div v-else style="width: 100%; height: calc(100% -  173px);" v-html="response"></div>
                                </el-tab-pane>
                            </el-tabs>
                        </el-card>
                    </el-col>
                    <el-col :span="12">
                        <el-alert type="info" :closable="false">
                            <template slot="title">
                                <i v-if="currentApiInfo.auth === 'strength'" style="color:#F56C6C;font-size: 20px;position: relative;right: 5px;top: 2px;" class="el-icon-s-check"></i>
                                <i v-if="currentApiInfo.auth === 'weak'"  style="color:#67C23A;font-size: 20px;position: relative;right: 5px;top: 2px;" class="el-icon-s-check"></i>
                                <i v-if="!currentApiInfo.auth"  style="font-size: 20px;position: relative;right: 5px;top: 2px;" class="el-icon-s-check"></i>
                                <el-tag type="danger" v-if="currentApiInfo.method.toUpperCase() === 'GET'" effect="dark">GET</el-tag>
                                <el-tag type="warning" v-if="currentApiInfo.method.toUpperCase() === 'POST'" effect="dark">POST</el-tag>
                                <span style="font-size: 18px;color: #606266">{{currentApiInfo.name}}</span>
                            </template>
                            <?php if(\App\Util\AdminAuth::isSuper()) { ?>
                                <span>
                                    <el-link>请求位置：</el-link> <el-link type="primary">{{currentApiInfo.call}}</el-link>
                                </span>
                            <?php } ?>
                        </el-alert>
                        <el-alert id="url" type="success" :closable="false">
                            <template slot="title" >
                                <div>
                                    <el-link><b>请求地址：</b></el-link>
                                </div>
                                <div>
                                    <el-link type="primary"><b>URL:</b> <i>{{ApiInfo.host + currentApiInfo.url}}</i></el-link>
                                    <el-link>  <i class="el-icon-document-copy" @click="copy(ApiInfo.host + currentApiInfo.url)"></i></el-link>
                                </div>
                                <div>
                                    <el-link type="primary"><b>Path:</b> <i>{{ currentApiInfo.url }}</i> </el-link>
                                    <el-link> <i class="el-icon-document-copy" @click="copy(currentApiInfo.url)"></i></el-link>
                                </div>
                            </template>
                        </el-alert>

                        <el-tabs v-model="paramInfo" @tab-click="paramToggle">
                            <el-tab-pane label="请求参数" name="request">

                                <el-table
                                        :data="currentApiInfo.requestParams"
                                        style="width: 100%;margin-bottom: 20px;"
                                        row-key="name"
                                        border
                                        :row-class-name="tableVersionClass"
                                        size="mini"
                                        default-expand-all>
                                    <el-table-column prop="name" label="字段" ></el-table-column>
                                    <el-table-column prop="type" align="center" label="类型" :width="80" ></el-table-column>
                                    <el-table-column prop="required" align="center" :width="60" label="必填" >
                                        <template #default="scope">{{ scope.row.required ? "是" : "否" }}</template>
                                    </el-table-column>
                                    <el-table-column prop="version" align="center" :width="60" label="版本" ></el-table-column>
                                    <el-table-column prop="describe" label="描述"  ></el-table-column>
                                </el-table>
                            </el-tab-pane>
                            <el-tab-pane label="响应参数" name="response">
                                <el-table
                                        :data="currentApiInfo.responseParams"
                                        style="width: 100%;margin-bottom: 20px;"
                                        row-key="name"
                                        border
                                        size="mini"
                                        :row-class-name="tableVersionClass"
                                        default-expand-all>
                                    <el-table-column prop="name" label="字段"></el-table-column>
                                    <el-table-column prop="type" align="center" label="类型" :width="80" ></el-table-column>
                                    <el-table-column prop="required" align="center" :width="60" label="必填" >
                                        <template #default="scope">{{ scope.row.required ? "是" : "否" }}</template>
                                    </el-table-column>
                                    <el-table-column prop="version" align="center" :width="60" label="版本" ></el-table-column>
                                    <el-table-column prop="describe" label="描述"  ></el-table-column>
                                </el-table>
                            </el-tab-pane>
                        </el-tabs>

                    </el-col>
                </el-row>
            </el-main>
        </el-container>
    </el-container>
    <el-dialog title="文档说明" :visible.sync="docDesVisible" @close="docDesVisibleClose">
        <el-alert title="页面介绍" type="success"   :closable="false">
            <div><i class="el-icon-star-on" style="color: #E6A23C"></i> 已做标记，可认为是收藏，接口详情点击可切换标记状态</div>
            <div><i class="el-icon-star-off" style="color: #E6A23C"></i> 未做标记</div>
            <div><i style="color:#F56C6C;" class="el-icon-s-check"></i>必须使用Token验证</div>
            <div><i style="color:#67C23A;" class="el-icon-s-check"></i>使用Token数据, 不验证</div>
            <div><i style="color:#909399;" class="el-icon-s-check"></i> 无需token</div>
            <div><i class="el-icon-s-promotion"></i> 发送模拟请求</div>
        </el-alert>
        <el-alert title="响应介绍" type="warning"   :closable="false">
            <div><b>code:</b> 状态码</div>
            <div><b>data:</b> 数据</div>
            <div><b>msg:</b> 响应提示语</div>
            <div><b>ts:</b> 当前时间戳（秒）</div>
            <hr>
            <div><b>code 值说明:</b></div>
            <div><b style="margin-left: 10px">200:</b>成功</div>
            <div><b style="margin-left: 10px">202:</b>失败</div>
            <div><b style="margin-left: 10px">1001:</b>刷新token,此时响应数据会带上新的token</div>
            <div><b style="margin-left: 10px">1002:</b>token无效，应要求用户重新登录</div>
        </el-alert>
        <el-alert title="认证说明" type="error"   :closable="false">
            <div>认证方式采用JWT方式， 传输方式使用Header参数： Authenticate</div>
            <div><b>例：</b>Authenticate: Bearer G0peyJhbGciF7hOiJyaXB.G0peyJhbGciF7hOiJyaXB.G0peyJhbGciF7hOiJyaXB</div>
        </el-alert>
    </el-dialog>
</div>
</body>

<script>
    const APP = new Vue({
        el:'#app',
        data: {
            docDesVisible:!localStorage.getItem("docDesVisible"),
            paramInfo: 'request',
            paramShowType: 'form',
            requestUrl: 'https://vuejs.org/guide/introduction.html',
            sendIcon: 'el-icon-s-promotion',
            form: {},
            paramShowJsonEl:'',
            responseJsonEl:'',
            scriptBeforeRequestEl:'',
            scriptAfterRequestEl:'',
            scriptBeforeRequest:`// 请求之前会调用此段代码\n// requestData 对象包含请求参数，可在此更改,异步请求库为：axios \n// ApiInfo 对象包含接口信息\n// console.log(requestData);\n// console.log(ApiInfo);`,
            scriptAfterRequest:`// 请求之后调用此段代码\n// ApiInfo 对象包含接口信息\n// response 对象包含响应的数据\n// console.log(ApiInfo);\n// console.log(response);`,
            response:{},
            currentApiInfo:{},
            ApiInfo:{},
            myMark:[],
            showMark:false,
            searchText:"",
            apiActive:-1,
            version: 1
        },
        created(){
            if (localStorage.getItem("ScBefore")){
                this.scriptBeforeRequest = localStorage.getItem("ScBefore");
            }
            if (!/requestData\.headers/.test(this.scriptBeforeRequest)) {
                this.scriptBeforeRequest += `\nif(ApiInfo.auth){\n`;
                this.scriptBeforeRequest += `    requestData.headers = { Authorization: 'Bearer ' + localStorage.getItem("token")};\n`;
                this.scriptBeforeRequest += `}`;
            }
            if (localStorage.getItem("ScAfter")){
                this.scriptAfterRequest = localStorage.getItem("ScAfter");
            }
            if (!/response\.data\.token/.test(this.scriptAfterRequest)) {
                this.scriptAfterRequest += `\nif(response.data.token){\n`
                    + `    localStorage.setItem("token", response.data.token.token);\n`
                    + `}`;
            }

            this.getApiInfo();
            if (localStorage.getItem("myMark")) {
                this.myMark = localStorage.getItem("myMark").split(',');
            }
        },
        mounted(){

        },
        methods: {
            mark(url){
                let index = this.myMark.indexOf(url);
                if(index >= 0){
                    this.myMark.splice(index, 1);
                }else{
                    this.myMark.push(url);
                }
                localStorage.setItem("myMark", this.myMark);
            },
            searchP(item){
                let show = true;
                if(this.showMark && item.children.filter(a => this.myMark.includes(a.url)).length === 0){
                    show = false;
                }
                if(this.searchText){
                    let length = item.children.filter(a => {
                        return a.url.indexOf(this.searchText) >= 0 || a.name.indexOf(this.searchText) >= 0
                    }).length;
                    if (length <= 0) show = false;
                }
                if (this.version && !item.version.includes(this.version * 1) && !item.version.includes(-this.version)) {
                    show = false;
                }
                return show;
            },
            docDesVisibleClose(){
                localStorage.setItem('docDesVisible', "1");
            },
            search(a){
                let show = true;
                if(this.showMark && !this.myMark.includes(a.url)){
                    show = false;
                }
                if(this.searchText && (a.url.indexOf(this.searchText) < 0 && a.name.indexOf(this.searchText) < 0)){
                    show = false;
                }
                if(this.version && !a.version.includes(this.version * 1) && !a.version.includes(-this.version)) {
                    show = false;
                }
                return show;
            },
            handleSelect(key, keyPath) {
                let s = key.split('-')
                let currentApiInfo = this.ApiInfo.apiLists[s[0]].children[s[1]];
                this.currentApiInfo = JSON.parse(JSON.stringify(currentApiInfo));
                this.form = this.requestParamsResolve(this.currentApiInfo.requestParams);
                this.paramShowJsonEl && this.paramShowJsonEl.set(this.form);
            },
            requestParamsResolve(params){
                let forms = {};
                for (let i = 0; i < params.length; i++) {
                    switch (params[i].type) {
                        case 'Object':
                            forms[params[i].name] = this.requestParamsResolve(params[i].children ? params[i].children : []);
                            break;
                        case 'Array':
                            forms[params[i].name] = [];
                            if(params[i].children && params[i].children.length > 0){
                                forms[params[i].name].push(this.requestParamsResolve(params[i].children));
                            }
                            break;
                        case 'Integer':
                        case 'float':
                            forms[params[i].name] = 1;
                            break;
                        default:
                            forms[params[i].name] = '';
                    }
                }

                return forms;
            },
            handleClose(key, keyPath) {
                console.log(key, keyPath);
            },
            paramToggle(el){
                console.log(el)
            },
            paramShowTypeToggle(el){
                if (el.name === 'json') {
                    setTimeout(() => {
                        if (!this.paramShowJsonEl) {
                            this.paramShowJsonEl = new JSONEditor(
                                document.getElementById("paramShowJson"),
                                this.jsonEditorInitOptions('code', (jsonString) => {
                                    this.form = JSON.parse(jsonString);
                                })
                            );
                        }

                        setTimeout(() => this.paramShowJsonEl.set(this.form), 5);
                    }, 10);
                } else if (el.name === 'scriptBefore') {
                    if (!this.scriptBeforeRequestEl) {
                        let myTextArea = document.getElementById('requestBefore');
                        this.scriptBeforeRequestEl = CodeMirror.fromTextArea(myTextArea, {
                            lineNumbers: true,
                            mode: 'text/javascript',
                            theme: 'material',
                            styleActiveLine: true,
                            lineWrapping: true
                        });

                        this.scriptBeforeRequestEl.on('blur', (codemirror) => {
                            localStorage.setItem("ScBefore", codemirror.getValue());
                            this.scriptBeforeRequest = codemirror.getValue();
                        });

                        setTimeout(() => {
                            this.scriptBeforeRequestEl.setValue(this.scriptBeforeRequest);
                            this.scriptBeforeRequestEl.refresh();
                        }, 10)
                    }
                } else if (el.name === 'scriptAfter') {
                    if (!this.scriptAfterRequestEl) {
                        let myTextArea = document.getElementById('requestAfter');
                        this.scriptAfterRequestEl = CodeMirror.fromTextArea(myTextArea, {
                            lineNumbers: true,
                            mode: 'text/javascript',
                            theme: 'material',
                            styleActiveLine: true,
                            lineWrapping: true,
                        });
                        this.scriptAfterRequestEl.on('blur',  (codemirror) => {
                            localStorage.setItem("ScAfter", codemirror.getValue());
                            this.scriptAfterRequest = codemirror.getValue();
                        });

                        setTimeout(() => {
                            this.scriptAfterRequestEl.setValue(this.scriptAfterRequest);
                            this.scriptAfterRequestEl.refresh();
                        }, 10)
                    }
                }
            },
            send(){
                this.sendIcon = 'el-icon-loading';
                this.paramShowType = 'response';
                let requestData = {
                    url: this.currentApiInfo.url,
                    method:this.currentApiInfo.method
                };
                if (this.form){
                    this.currentApiInfo.method.toUpperCase() === 'GET'
                        ? requestData.params = this.form
                        : requestData.data   = this.form;
                }

                let ApiInfo = JSON.parse(JSON.stringify(this.currentApiInfo));
                try{
                    eval(this.scriptBeforeRequest);
                }catch (e) {
                    console.log(e)
                }

                axios(requestData).then(res => {
                    this.response = res.data;
                    this.sendIcon = 'el-icon-s-promotion';
                    this.responseJsonHandle();
                    try{
                        let response = JSON.parse(JSON.stringify(res.data));
                        eval(this.scriptAfterRequest);
                    }catch (e) {
                        console.log(e)
                    }
                }).catch((error) => {
                    this.response = error;
                    this.sendIcon = 'el-icon-s-promotion';
                    this.responseJsonHandle();
                });
            },
            responseJsonHandle(){
                setTimeout(() => {
                    if (typeof this.response === 'object') {
                        if (!this.responseJsonEl) {
                            this.responseJsonEl = new JSONEditor(
                                document.getElementById("responseJson"),
                                this.jsonEditorInitOptions('code')
                            );
                        }
                        console.log(this.response);
                        this.responseJsonEl.set(this.response);
                    } else {
                        this.responseJsonEl = null;
                    }
                }, 10);
            },
            jsonEditorInitOptions(defaultMode = 'tree', change){
                return {
                    mode: defaultMode,
                    modes: ['code', 'form', 'text', 'tree', 'view', 'preview'], // allowed modes
                    onChangeText: change,
                };
            },
            copy(text){
                navigator.clipboard.writeText(text);
                this.$message.success('复制成功');
            },
            exportApi(file){
                let reader = new FileReader()
                reader.readAsText(file.raw,'UTF-8');
                reader.onload = function (e) {
                    try {
                        let urlData = JSON.parse(this.result);
                    }catch(e){
                        APP.$message.error('识别失败');
                    }
                };
            },
            getApiInfo(){
                axios({
                    url:"<?= \App\Util\Hy::route()->to([\Plugins\ApiDoc\Http\Admin\Controller\Admin\DocumentController::class, 'doc']) ?>"
                }).then(res => {
                    this.ApiInfo = res.data.data;
                    this.$message.success("已获取最新文档信息，当前最新版本：" + Math.max(...res.data.data.version))
                })
            },
            tableVersionClass({row, rowIndex}){
                if(row.version === 1) return '';
                return row.version > 1 ? 't-add' : 't-del';
            }
        }
    });



</script>
</html>